// The libMesh Finite Element Library.
// Copyright (C) 2002-2018 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner

// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.

// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.

// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

// Code partially copyright Edd Dawson 2007
//
// Boost Software License - Version 1.0 - August 17th, 2003
//
// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:
//
// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software, unless such copies or derivative
// works are solely in the form of machine-executable object code generated by
// a source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.


#include "libmesh/libmesh_config.h"
#include "libmesh/print_trace.h"
#include "libmesh/libmesh.h"

#include <unistd.h>  // needed for getpid()
#include <fstream>
#include <sstream>
#include <string>
#include <cstdio> // std::remove
#include <cstdlib> // std::system
#include <sys/types.h> // pid_t

#if defined(LIBMESH_HAVE_GLIBC_BACKTRACE)
#include <execinfo.h>
#endif

#if defined(LIBMESH_HAVE_GCC_ABI_DEMANGLE)
#include <cxxabi.h>
#endif

// Anonymous namespace for print_trace() helper functions
namespace
{

// process_trace() is a helper function used by
// libMesh::print_trace().  It is only available if configure
// determined your compiler supports backtrace(), which is a GLIBC
// extension.
#if defined(LIBMESH_HAVE_GLIBC_BACKTRACE)
std::string process_trace(const char * name)
{
  std::string fullname = name;
  std::string saved_begin, saved_end;
  size_t namestart, nameend;

  // The Apple backtrace function returns more information than the Linux version.
  // We need to pass only the function name to the demangler or it won't decode it for us.
  //
  // lineno: stackframeno address functionname + offset

#ifdef __APPLE__
  namestart = fullname.find("0x");
  if (namestart != std::string::npos)
    {
      namestart = fullname.find(' ', namestart) + 1;
      saved_begin = fullname.substr(0, namestart);
    }
  else
    namestart = 0;
  nameend = fullname.find('+');
  if (nameend == std::string::npos ||
      nameend <= namestart)
    nameend = fullname.size();
  else
    {
      nameend -= 1;
      saved_end = fullname.substr(nameend, fullname.length());
    }
#else
  namestart = fullname.find('(');
  if (namestart == std::string::npos)
    return fullname;
  else
    namestart++;
  nameend = fullname.find('+');
  if (nameend == std::string::npos ||
      nameend <= namestart)
    return fullname;
#endif

  std::string type_name = fullname.substr(namestart, nameend - namestart);

  // Try to demangle now
  return saved_begin + libMesh::demangle(type_name.c_str()) + saved_end;
}
#endif



// gdb_backtrace() is used by libMesh::print_trace() to try and get a
// "better" backtrace than what the backtrace() function provides.
// GDB backtraces are a bit slower, but they provide line numbers in
// source code, a really helpful feature when debugging something...
bool gdb_backtrace(std::ostream & out_stream)
{
#ifdef LIBMESH_GDB_COMMAND
  // Eventual return value, true if gdb succeeds, false otherwise.
  bool success = true;

  // The system() call does not allow us to redirect the output to a
  // C++ ostream, so we redirect gdb's output to a (known) temporary
  // file, and then send output that to the user's stream.
  char temp_file[] = "temp_print_trace.XXXXXX";
  int fd = mkstemp(temp_file);

  // If mkstemp fails, we failed.
  if (fd == -1)
    success = false;
  else
    {
      // Run gdb using a system() call, redirecting the output to our
      // temporary file.
      pid_t this_pid = getpid();

      int exit_status = 1;

      libmesh_try
        {
          std::string gdb_command =
            libMesh::command_line_value("gdb",std::string(LIBMESH_GDB_COMMAND));

          std::ostringstream command;
          command << gdb_command
                  << " -p "
                  << this_pid
                  << " -batch -ex bt -ex detach 2>/dev/null 1>"
                  << temp_file;
          exit_status = std::system(command.str().c_str());
        }
      libmesh_catch (...)
        {
          std::cerr << "Unable to run gdb" << std::endl;
        }

      // If we can open the temp_file, the file is not empty, and the
      // exit status from the system call is 0, we'll assume that gdb
      // worked, and copy the file's contents to the user's requested
      // stream.  This rdbuf() thing is apparently how you do
      // this... Otherwise, report failure.
      std::ifstream fin(temp_file);
      if (fin && (fin.peek() != std::ifstream::traits_type::eof()) && (exit_status == 0))
        out_stream << fin.rdbuf();
      else
        success = false;
    }

  // Clean up the temporary file, regardless of whether it was opened successfully.
  std::remove(temp_file);

  return success;
#else
  return false;
#endif
}

} // end anonymous namespace


namespace libMesh
{

void print_trace(std::ostream & out_stream)
{
  // First try a GDB backtrace.  They are better than what you get
  // from calling backtrace() because you don't have to do any
  // demangling, and they include line numbers!  If the GDB backtrace
  // fails, for example if your system does not have GDB, fall back to
  // calling backtrace().
  bool gdb_worked = false;

  // Let the user disable GDB backtraces by configuring with
  // --without-gdb-command or with a command line option.
  if (std::string(LIBMESH_GDB_COMMAND) != std::string("no") &&
      !libMesh::on_command_line("--no-gdb-backtrace"))
    gdb_worked = gdb_backtrace(out_stream);

  // This part requires that your compiler at least supports
  // backtraces.  Demangling is also nice, but it will still run
  // without it.
#if defined(LIBMESH_HAVE_GLIBC_BACKTRACE)
  if (!gdb_worked)
    {
      void * addresses[40];
      char ** strings;

      int size = backtrace(addresses, 40);
      strings = backtrace_symbols(addresses, size);
      out_stream << "Stack frames: " << size << std::endl;
      for (int i = 0; i < size; i++)
        out_stream << i << ": " << process_trace(strings[i]) << std::endl;
      std::free(strings);
    }
#endif
}


// If tracefiles are enabled, calls print_trace() and sends the
// result to file.  Otherwise, does nothing.
void write_traceout()
{
#ifdef LIBMESH_ENABLE_TRACEFILES
  std::stringstream outname;
  outname << "traceout_" << static_cast<std::size_t>(libMesh::global_processor_id()) << '_' << getpid() << ".txt";
  std::ofstream traceout(outname.str().c_str(), std::ofstream::app);
  libMesh::print_trace(traceout);
#endif
}



// demangle() is used by the process_trace() helper function.  It is
// also used by the Parameters class for demangling typeid's.  If
// configure determined that your compiler does not support
// demangling, it simply returns the input string.
#if defined(LIBMESH_HAVE_GCC_ABI_DEMANGLE)
std::string demangle(const char * name)
{
  int status = 0;
  std::string ret = name;

  // Actually do the demangling
  char * demangled_name = abi::__cxa_demangle(name, 0, 0, &status);

  // If demangling returns non-nullptr, save the result in a string.
  if (demangled_name)
    ret = demangled_name;

  // According to cxxabi.h docs, the caller is responsible for
  // deallocating memory.
  std::free(demangled_name);

  return ret;
}
#else
std::string demangle(const char * name) { return std::string(name); }
#endif

} // namespace libMesh
